#include "keyword.h"
#include "unicode.h"
#include "debug.h"
#include "util.h"

#include <algorithm>

KeywordList keyword_list;

wxString Keyword::ToString() const
{
    return wxFormat("keyword #%d", id);
}

void Keyword::Created() {created = time(0);}
void Keyword::Modified() {modified = time(0);}
void Keyword::Used() {used = time(0);}


static bool IsSeparatorChar(wchar_t c)
{
    return (c=='\n' || c==',');
}

bool CheckKeyword(const wxString& keyword)
{
    unsigned x;

    // skip initial blanks, to test for empty keyword
    for (x=0; x<keyword.Len() && IsBlankChar(keyword[x]); x++);
    if (x == keyword.Len()) {
        return false;
    }

    // test rest of keyword, allow blanks
    for (; x<keyword.Len(); x++) {
        if (!IsBlankChar(keyword[x]) && !IsKeywordChar(keyword[x])) {
            return false;
        }
    }
    return true;
}

bool CheckKeywordsString(const wxString& from, const wxString& extra_chars)
{
    bool in_keyword=false;
    bool has_keyword=false;
    for (unsigned x=0; x<from.Len(); x++) {
        if (IsKeywordChar(from[x]) || extra_chars.Find(from[x])!=wxNOT_FOUND) {
            in_keyword = true;
            has_keyword = true;
            continue;
        }
        if (IsBlankChar(from[x]))
            continue;
        if (IsSeparatorChar(from[x])) {
            in_keyword = false;
            continue;
        }
        dbg("Note::CheckKeywordsString(): Invalid keyword character: %d\n", from[x]);
        return false;   // disallowed char
    }
    if (!has_keyword)   // allow empty list
        return true;
    return in_keyword;  // disallow empty keyword at the end of list
}

bool KeywordsFromString(const wxString& arg_from, std::vector<wxString>& keywords, const wxString& extra_chars)
{
    wxString from = arg_from;
    keywords.clear();

    // delete initial blanks
    while (!from.IsEmpty() && IsBlankChar(from[0]))
        from.Remove(0, 1);

    // allow empty keyword list
    if (from.IsEmpty())
        return true;

    // cycle through keywords
    while (!from.IsEmpty()) {

        // delete initial blanks in this keyword
        while (IsBlankChar(from[0])) from.Remove(0, 1);

        // read Keyword (keyword characters up to keyword separator or end of string)
        wxString keyword;
        while (!from.IsEmpty()) {
            if (IsSeparatorChar(from[0])) {
                from.Remove(0, 1);
                break;
            }
            if (IsKeywordChar(from[0]) || extra_chars.Find(from[0])!=wxNOT_FOUND || IsBlankChar(from[0])) {
                keyword += from[0];
                from.Remove(0, 1);
                continue;
            }
            return false;   // invalid character
        }

        // remove ending blanks from keyword
        while (IsBlankChar(keyword[keyword.Len()-1]))
            keyword.RemoveLast();
        if (keyword.IsEmpty())
            return false;   // empty keyword

        // save keyword
        keywords.push_back(keyword);
    }

    return true;
}

/*
static bool SeparateLastName(const wxString& name, wxString& result)
{
    wxASSERT(name.Len()>0);
    wxASSERT(IsKeywordChar(name[name.Len()-1]));
    int x = name.Len()-1;
    while (x>0 && IsKeywordChar(name[x])) x--;
    if (x == 0)
        return false;
    result = name.Mid(x+1);
    return true;
}*/



// add all hints with all suffixes to hints list
static const wchar_t* hint_suffix[] = {
    L"", L"a",
    L"e", L"em", L"emu", L"eho", L"ej",
    L"ho",
    L"m", L"mu",
    L"o", L"om", L"ou", L"ov", L"ova", L"ovci", L"ovcov", L"ovcom", L"ove", L"ovec", L"ovej",
    L"ovho", L"ovi", L"ovmu", L"ovo", L"ovom", L"ovou", L"ovu", L"ovych", L"ovym", L"ovymi",
    L"u",
    L"y", L"yho", L"ych", L"ym", L"ymu"     // Ursiny (Ursinymu) vs. Hrusovsky (Hrusovskemu)
};
const int HINT_SUFFIX_COUNT = sizeof(hint_suffix)/sizeof(hint_suffix[0]);

// Karol[ovi] Smidk[emu] / Smidke[mu] ? asi druhe
static const struct {const wchar_t* first; const wchar_t* second;} hint_suffix_pair[] = {
    {L"", L"a"},                        // Felix[] Vasecka[a]
    {L"", L"e"},                        // Karol[] Satk[e]
    {L"", L"o"},                        // Daniel[] Krn[o]
    {L"", L"y"},                        // Milos[] Hrusovsk[y]
    {L"a", L""},                        // Gejz[a] Weisz[]
    {L"a", L"eho"},                     // Milos[a] Hrusovsk[eho]
    {L"a", L"ho"},                      // Jan[a] Ursiny[ho]
    {L"a", L"u"},                       // Daniel[a] Krn[u]
    {L"e", L"ej"},                      // Helene[e] Kyselicov[ej]
    {L"o", L""},                        // Jan[o] Ursiny
    {L"om", L"em"},                     // Karol[om] Satk[em]
    {L"om", L"ym"},                     // Jan[om] Ursin[ym], Milos[om] Hrusovsk[ym]
    {L"ovi", L"em"},                    // Karol[ovi] Satk[em]
    {L"ovi", L"emu"},                   // Milos[ovi] Hrusovsk[emu]
    {L"ovi", L"om"},                    // Milos[ovi] Hrusovsk[om]
    {L"ovi", L"ym"},                    // Jan[ovi] Usrin[ym]
    {L"ovi", L"ymu"},                   // Jan[ovi] Usrin[ymu]
    {L"u", L"a"}                        // Ott[u] Krajnak[a]
};
const int HINT_SUFFIX_PAIR_COUNT = sizeof(hint_suffix_pair)/sizeof(hint_suffix_pair[0]);

// get all possible hint patterns which would match specific hint variant
// wicked way, but turned out to be best option.
//   eg. Husakova -> Husakova, Husakova*, Husakov*, Husak*
// Hint can containt multiple words
//   eg. Jan Ursiny -> Jan Ursiny, Jan Ursiny*, Jan* Ursiny, Jan* Ursiny*, Jan Ursin*, Jan* Ursin*
static void GetHintPatterns(const wxString hint, set<wxString>& patterns, int from=0,
                            const vector<wxString> expected_suffixes = vector<wxString>())
{
    const int hintlen = hint.Len();
    wxASSERT(from <= hintlen);

    // skip initial blanks
    while (from<hintlen && (hint[from]==' ' || hint[from]=='-'))
        from++;

    // if we are at the end, save current pattern
    if (from == hintlen) {
        patterns.insert(hint);
        return;
    }

    // find end of (space-delimited) word starting at 'from'
    int to;
    for (to=from; to<hintlen && hint[to]!=' ' && hint[to]!='-'; to++);
    wxASSERT(to > from);
    const wxString word = hint.Mid(from, to-from);

    // pattern with no suffix on this word
    GetHintPatterns(hint, patterns, to, expected_suffixes);

    // if we have expected suffixes, check if current word matches any of them
    for (unsigned sufind=0; sufind<expected_suffixes.size(); sufind++) {
        const wxString& suffix = expected_suffixes[sufind];
        if (word.EndsWith(suffix)) {
            int suflen = suffix.Len();

            // replace suffix
            wxString hint2 = hint;
            hint2.replace(to-suflen, suflen, "*");

            // if suffix is second from pair, expect only that one in all further words
            if (sufind > 0) {
                vector<wxString> expsuf2;
                expsuf2.push_back(expected_suffixes[sufind]);
                GetHintPatterns(hint2, patterns, to-suflen+1, expsuf2);
            } else
                GetHintPatterns(hint2, patterns, to-suflen+1, expected_suffixes);
        }
    }

    // if we don't have expected suffix (this is the first word with suffix), try all possible suffixes
    if (expected_suffixes.size() == 0) {
        for (int sufind=0; sufind<HINT_SUFFIX_COUNT; sufind++) {
            const wxString suffix = hint_suffix[sufind];
            if (word.EndsWith(suffix)) {
                const int suflen = suffix.Len();

                // replace suffix
                wxString hint2 = hint;
                hint2.replace(to-suflen, suflen, "*");

                // if this suffix is first from suffix pair, save all matching seconds
                vector<wxString> expected_suffixes2;
                expected_suffixes2.push_back(suffix);
                for (int x=0; x<HINT_SUFFIX_PAIR_COUNT; x++)
                    if (hint_suffix_pair[x].first == suffix)
                        expected_suffixes2.push_back(hint_suffix_pair[x].second);

                // try with same suffix (or suffix pair) on rest of hint
                GetHintPatterns(hint2, patterns, to-suflen+1, expected_suffixes2);
            }
        }
    }
}

void KeywordList::AddKeyword(const Keyword& k)
{
    // add keyword to vector
    int index = keywords.size();
    keywords.push_back(k);

    // add ID
    wxASSERT(ids.find(k.id) == ids.end());
    ids[k.id] = index;

    // add name
    wxASSERT(names.find(k.name) == names.end());
    names[k.name] = k.id;

    // add hint patterns
    vector<wxString> hints_vector;
    wxASSERT(CheckKeywordsString(k.hints, "*"));
    KeywordsFromString(k.hints, hints_vector, "*");
    for (unsigned x=0; x<hints_vector.size(); x++) {
        set<int>& hint_ids = hints[hints_vector[x]];
        if (hint_ids.find(k.id) != hint_ids.end()) {
            WarnMsg(_("Keyword #%u ('%s') contains hint pattern '%s' multiple times"), k.id, k.name, hints_vector[x]);
            // TODO: Check this in keyword creation - it causes crashes!
        }
        hint_ids.insert(k.id);
    }
}

void KeywordList::RemoveKeyword(int id)
{

    // locate keyword by ID, and delete it from IDs
    wxASSERT(ids.find(id) != ids.end());
    int index = ids[id];
    ids.erase(ids.find(id));

    // delete it from keywords
    wxASSERT(index < (int)keywords.size());
    wxASSERT(keywords[index].id == id);
    Keyword k = keywords[index];
    keywords.erase(keywords.begin() + index);

    // fix indexes in ids table
    for (map<int,int>::iterator x=ids.begin(); x!=ids.end(); x++) {
        wxASSERT(x->second != index);
        if (x->second > index)
            x->second--;
    }

    // remove hint patterns
    wxASSERT(CheckKeywordsString(k.hints, "*"));
    vector<wxString> hints_vector;
    KeywordsFromString(k.hints, hints_vector, "*");
    for (unsigned x=0; x<hints_vector.size(); x++) {
        const wxString& hint = hints_vector[x];

        wxASSERT(hints.find(hint) != hints.end());
        set<int>& hint_variant_ids = hints[hint];

        wxASSERT(hint_variant_ids.find(k.id) != hint_variant_ids.end());
        hint_variant_ids.erase(hint_variant_ids.find(k.id));
        if (hint_variant_ids.empty())
            hints.erase(hints.find(hint));
    }

    // remove name
    wxASSERT(names.find(k.name) != names.end());
    wxASSERT(names[k.name] == k.id);
    names.erase(names.find(k.name));
}

bool KeywordList::KeywordsByAlias(vector<Keyword>& result, const wxString& alias) const
{
    bool found=false;
    for (int i=0; i<(int)keywords.size(); i++) {
        if (keywords[i].aliases.IsEmpty())              // common case optimization
            continue;
        if (wxString_FindI(keywords[i].aliases, alias) != wxNOT_FOUND) {
            if (wxString_FindI(keywords[i].name, alias) == wxNOT_FOUND) {
                result.push_back(keywords[i]);
                found=true;
            }
        }
    }
    return found;
}

void KeywordList::RecreateTables()
{
    vector<Keyword> new_keywords = keywords;

    keywords.clear();
    ids.clear();
    names.clear();
    hints.clear();

    for (unsigned x=0; x<new_keywords.size(); x++)
        AddKeyword(new_keywords[x]);
}

void KeywordList::ReplaceKeyword(int id, const Keyword& new_keyword)
{
    wxASSERT(ids.find(id) != ids.end());
    wxASSERT(ids[id] >= 0 && ids[id] < (int)keywords.size());
    RemoveKeyword(new_keyword.id);
    AddKeyword(new_keyword);
}

bool KeywordList::KeywordsByPartialName(vector<Keyword>& result, const wxString& name) const
{
    wxASSERT(!name.IsEmpty());
    for (unsigned id=0; id<keywords.size(); id++)
        if (wxString_ContainsI(keywords[id].name, name))
            result.push_back(keywords[id]);
    return result.size() > 0;
}

bool KeywordList::KeywordsByHint(vector<Keyword>& result, const wxString& hint) const
{
    wxASSERT(result.size()==0);

    // get all possible patterns which fit our hint (I know...)
    set<wxString> patterns;
    GetHintPatterns(hint, patterns);

    // get all keywords which contain any of those patterns
    set<int> result_ids;
    for (set<wxString>::const_iterator pattern_it=patterns.begin(); pattern_it!=patterns.end(); pattern_it++) {
        map<wxString, set<int> >::const_iterator pattern_ids_it = hints.find(*pattern_it);
        if (pattern_ids_it != hints.end()) {
            set<int> pattern_ids = pattern_ids_it->second;
            result_ids.insert(pattern_ids.begin(), pattern_ids.end());
        }
    }
    if (result_ids.size() == 0)
        return false;

    KeywordsByIds(result, result_ids);

    return true;
}

const Keyword& KeywordList::KeywordById(int id) const
{
    map<int, int>::const_iterator ids_it = ids.find(id);
    wxASSERT(ids_it != ids.end());
    int ind = ids_it->second;
    wxASSERT(ind >= 0 && ind < (int)keywords.size());
    return keywords[ind];
}

void KeywordList::KeywordUsed(int id)
{
    _KeywordById(id).Used();
}
